#!/usr/bin/env bash

## alias
alias cf='cf_help_tree'
alias cf_a_path='echo -e ${PATH//:/\\n}'

## help
# list all modules and functions
function cf_help_tree() {
  for module in $(cf_help_modules); do
    local funcs=($(cf_help_functions ${module#cf_*}))
    local length=${#funcs[@]}

    echo "$module($length)"
    for func in "${funcs[@]}"; do
      if [ "$func" = "${funcs[-1]}" ]; then
        echo "└── $func"
      else
        echo "├── $func"
      fi
    done
  done
}

# list all modules
function cf_help_modules() {
  if _cf_is_bash >/dev/null; then
    declare -F | awk '{print $NF}' | grep "^cf_" | awk -F '_' '{print $1i$2}' i='_' | sort | uniq
  elif _cf_is_zsh >/dev/null; then
    print -l ${(ok)functions} | grep "^cf_" | awk -F '_' '{print $1i$2}' i='_' | sort | uniq
  else
    echo "unsupported shell" && return 1
  fi
}

# list all functions
function cf_help_functions() {
  local module=""
  if [ "x$1" != "x" ] ; then
    local module="$1_"
  fi

  if _cf_is_bash >/dev/null; then
    declare -F | awk '{print $NF}' | grep "^cf_$module" | sort
  elif _cf_is_zsh >/dev/null; then
    print -l ${(ok)functions} | grep "^cf_$module" | sort
  else
    echo "unsupported shell" && return 1
  fi
}

# list all alias
function cf_help_alias() {
  alias | grep cf_a_
}

########################### ps ###########################

function cf_ps_time() {
  local pid=$1
  ! _cf_is_pid_exists >/dev/null $pid && echo "pid:$pid not exists" && return 1

  local elapsed="$(ps -oetime= -p $pid | _cf_s_trim)"
  local started="$(ps -olstart= -p $pid | _cf_s_trim)"
  if [ `_cf_is_gnu_date` = "true" ]; then
    started=$(date +'%Y-%m-%d %H:%M:%S' -d "$started")
  fi
  local cpu_time=$(ps -otime= -p $pid | _cf_s_trim)
  echo "started from: $started, elapsed: $elapsed, cumulative cpu time: $cpu_time"
}

function cf_ps_env() {
  local pid=$1
  ! _cf_is_pid_exists >/dev/null $pid && echo "pid:$pid not exists" && return 1

  if _cf_is_linux >/dev/null; then
    xargs --null --max-args=1 < /proc/$pid/environ
  elif _cf_is_darwin >/dev/null; then
    ps -wwE -p $pid
  else
    echo "unsupported os" && return 1
  fi
}

function cf_ps_stdio() {
  local pid=$1
  /usr/sbin/lsof -a -p $pid -d 0,1,2
}

function cf_ps_stdout() {
  local pid=$1
  if _cf_is_linux >/dev/null; then
    readlink -f /proc/$pid/fd/1
  elif _cf_is_darwin >/dev/null; then
    /usr/sbin/lsof -a -p $pid -d 1 | awk 'NR>1{print $NF}'
  else
    echo "unsupported os" && return 1
  fi
}

function cf_ps_zombies() {
  ps -opid,state,command -e | awk 'NR==1 || $2=="Z"'
}

########################### os ###########################

function cf_os_dmesg() {
  ! _cf_is_linux >/dev/null && echo "only works in linux" && return 1

  dmesg -T "$@" 2>/dev/null
  [ $? -eq 0 ] && return 0

  dmesg "$@" | perl -w -e 'use strict;
  my ($uptime) = do { local @ARGV="/proc/uptime";<>}; ($uptime) = ($uptime =~ /^(\d+)\./);
  foreach my $line (<>) {
    printf( ($line=~/^\[\s*(\d+)\.\d+\](.+)/) ? ( "[%s]%s\n", scalar localtime(time - $uptime + $1), $2 ) : $line )
  }'
}

# get all ip addresses
function cf_os_iplist() {
  if [ -x /sbin/ip ]; then
    local list=$(/sbin/ip -o -4 addr list | awk '{print $4}' | cut -d'/' -f1 | tr '\n' ',')
  else
    local list=$(/sbin/ifconfig | grep "inet " | awk '{print $2}' | sed 's/addr://' | tr '\n' ',')
  fi
  echo ${list%,}
}

########################### is ###########################

function cf_is_available_port() {
  local port=$1
  if [[ "$OSTYPE" = *linux* ]];then
    local r=$( netstat -ant | awk '$6=="LISTEN" && $4~":'$port'$"' )
  elif [[ "$OSTYPE" = *darwin* ]];then
    local r=$( netstat -ant | awk '$6=="LISTEN"' | grep "\.$port " )
  else
    echo "unknown system" && return 1
  fi

  [ -z "$r" ] && echo "true" && return 0;
  echo "false" && return 1 # port has been used
}

########################### tool alias t ###########################

function cf_t_http_server() {
  local port=${1:-8001}
  python -mSimpleHTTPServer $port 2>/dev/null
}

########################### _cf ###########################

function _cf_trim() {
  local str="$1"
  local extglob=$(_cf_is_extglob_enabled)
  if _cf_is_bash >/dev/null || _cf_is_zsh >/dev/null; then
    [ $extglob = "false" ] && _cf_t_enable_extglob
    str="${str##*( )}"
    str="${str%%*( )}"
    [ $extglob = "false" ] && _cf_t_disable_extglob
  else
    echo "unsupported shell" && return 1
  fi
  echo $str
}

## is
# check current shell is bash
function _cf_is_bash() {
  [[ `_cf_t_shell_name` = "-bash" || `basename $(_cf_t_shell_name)` = "bash" ]] && echo "true" && return 0
  echo "false" && return 1
}

# check current shell is zsh
function _cf_is_zsh() {
  [[ `_cf_t_shell_name` = "-zsh" || `basename $(_cf_t_shell_name)` = "zsh" ]] && echo "true" && return 0
  echo "false" && return 1
}

# check current os is linux
function _cf_is_linux() {
  [[ "$OSTYPE" = *linux* ]] && echo "true" && return 0
  echo "false" && return 1
}

# check current os is mac/darwin
function _cf_is_darwin() {
  [[ "$OSTYPE" = *darwin* ]] && echo "true" && return 0
  echo "false" && return 1
}

# check current os is windows/cygwin
function _cf_is_cygwin() {
  [[ "$OSTYPE" = *cygwin* ]] && echo "true" && return 0
  echo "false" && return 1
}

function _cf_is_gnu_date() {
  date --version >/dev/null 2>&1 && echo "true" && return 0
  echo "false" && return 1
}

function _cf_is_gnu_sed() {
  sed --version >/dev/null 2>&1 && echo "true" && return 0
  echo "false" && return 1
}

function _cf_is_gnu_awk() {
  awk --version | grep GNU >/dev/null && echo "true" && return 0
  echo "false" && return 1
}

function _cf_is_gnu_grep() {
  grep --version | grep GNU >/dev/null && echo "true" && return 0
  echo "false" && return 1
}


# check pid is exists
function _cf_is_pid_exists() {
  local pid=$1
  [ -z "$pid" ] && echo "false" && return 1
  kill -0 $pid >/dev/null 2>&1 && echo "true" && return 0
  echo "false" && return 1
}

# check file name globbing flag
function _cf_is_glob_enabled() {
  if _cf_is_bash >/dev/null; then
    [[ $- != *f* ]] && echo "true" && return 0
  elif _cf_is_zsh >/dev/null; then
    [[ -o glob ]] && echo "true" && return 0
  else
    echo "unsupported shell" && return 1
  fi
  echo "false" && return 1
}

# check extglob flag
function _cf_is_extglob_enabled() {
  if _cf_is_bash >/dev/null; then
    shopt -q extglob && echo "true" && return 0
  elif _cf_is_zsh >/dev/null; then
    [[ -o kshglob ]] && echo "true" && return 0
  else
    echo "unsupported shell" && return 1
  fi
  echo "false" && return 1
}

## string alias s_
# trim string
# shellcheck disable=SC2120
function _cf_s_trim() {
  if [ -p /dev/stdin ]; then
    while IFS='' read line; do
      _cf_trim "$line"
    done
  elif [ $# -eq 1 ]; then
    _cf_trim "$1"
  else
    _cf_trim ""
  fi
}

# java style startsWith
function _cf_s_starts_with() {
  local str=$1
  local pre=$2
  [[ "$str" ==  ${pre}* ]]
}

# java style substring
function _cf_s_substring() {
  local str=$1
  local begin=$2
  local end=$3
  if [ -z "$end" ]; then
    echo ${str:$begin}
  else
    local len=`expr $end - $begin`
    echo ${str:$begin:$len}
  fi
}

## tools alias t_
# get current shell name
function _cf_t_shell_name() {
  local name=$( ps -ocommand= -p $$ | awk '{print $1}')
  if _cf_s_starts_with $name "-"; then
    _cf_s_substring $name 1
  else
    echo $name
  fi
}

# enable file name globbing
function _cf_t_enable_glob() {
  _cf_is_bash >/dev/null && set +f && return 0
  _cf_is_zsh >/dev/null && set -o glob && return 0
  echo "unsupported shell" && return 1
}

# disable file name globbing
function _cf_t_disable_glob() {
  _cf_is_bash >/dev/null && set -f && return 0
  _cf_is_zsh >/dev/null && set -o noglob && return 0
  echo "unsupported shell" && return 1
}

# enable extglob
function _cf_t_enable_extglob() {
  _cf_is_bash >/dev/null && shopt -s extglob && return 0
  _cf_is_zsh >/dev/null && set -o kshglob && return 0
  echo "unsupported shell" && return 1
}

# disable extglob
function _cf_t_disable_extglob() {
  _cf_is_bash >/dev/null && shopt -u extglob && return 0
  _cf_is_zsh >/dev/null && unsetopt kshglob && return 0
  echo "unsupported shell" && return 1
}
########################### java ###########################

function cf_java_pids() {
  ps x | grep "jav[a]" | awk '{print $1}'
}

function cf_java_infos() {
  for p in `cf_java_pids`; do
    echo "[java pid: $p]"
    info=`ps -opid=,command= -p $p | tr ' ' '\n' | awk '/-classpath|-cp/{getline;next};/-Xmx|-Dcatalina.base/{print};/^-/{next};1' | xargs`
    echo "  $info"
    time=`cf_ps_time $p`
    echo "  $time"
  done
}

function cf_java_sysprops() {
  local pid=$1
  ! _cf_is_pid_exists >/dev/null $pid && echo "pid:$pid not exists" && return 1
  "$JAVA_HOME"/bin/jinfo -sysprops $pid
}

function cf_java_threads() {
  local pid=$1
  local vm_threads="GC task|VM |CompilerThread|Finalizer|Reference Handler|Signal Dispatcher"
  "$JAVA_HOME"/bin/jstack $pid | grep "^\"" | grep -Ev "$vm_threads"
}

function cf_java_top() {
  ! _cf_is_linux >/dev/null && echo "only works in linux" && return 1

  local pid=$1
  ! _cf_is_pid_exists >/dev/null $pid && echo "pid:$pid not exists" && return 1
  "$JAVA_HOME"/bin/jstack $pid > "/tmp/.${pid}_jstack"

  local n=${2:-5}
  local tf="/tmp/.${pid}_top_threads"
  top -H -b -n 1 -p $pid | sed '1,/^$/d' | sed '1d;/^$/d' | grep -v $pid | sort -nrk9 | head -$n > $tf
  while read line; do
    echo $line | awk '{print "tid: "$1," cpu:%"$9}'
    local nid=`printf "%0x" $(echo "$line" | awk '{print $1}')`
    grep "$nid" -A20 "/tmp/.${pid}_jstack" | sed -n '1,/^$/p'
  done < $tf
}

# jstack series, parameters(pid, count, delay)
function cf_java_jstack_series() {
  local pid=$1
  ! _cf_is_pid_exists >/dev/null $pid && echo "pid:$pid not exists" && return 1
  local count=${2:-5}  # defaults 5 times
  local delay=${3:-0.5} # defaults 0.5 seconds

  local logdir=${LOG_DIR:-"/tmp"}
  while [ $count -gt 0 ]; do
    if _cf_is_gnu_date >/dev/null; then
      local suffix=$(date +%H%M%S.%N)
    else
      local suffix=$(date +%H%M%S)"."$count
    fi
    "$JAVA_HOME"/bin/jstack $pid > $logdir/jstack.$pid.$suffix
    sleep $delay
    let count--
    echo -n "."
  done
}

# class find, parameters(libdir, class name)
function cf_java_class_find() {
  local libdir=$1
  local name=$2
  local glob=$(_cf_is_glob_enabled)
  [ $glob = "false" ] && _cf_t_enable_glob
  builtin pushd $libdir >/dev/null
  for j in *.jar; do
    unzip -l $j | grep $name && echo $j;
  done
  builtin popd >/dev/null
  [ $glob = "false" ] && _cf_t_disable_glob
}

# classpath check, parameter(classpath dir)
function cf_java_classpath_check() {
  [ $# -eq 0 ] && echo "please enter classpath dir" && return 1
  [ ! -d "$1" ] && echo "not a directory" && return 1

  local tmpfile="/tmp/.cp$(date +%s)"
  local tmphash="/tmp/.hash$(date +%s)"
  local verbose="/tmp/cp-verbose.log"

  if _cf_is_zsh >/dev/null;then
    local -a files
    local begin=1
  elif _cf_is_bash >/dev/null;then
    declare -a files
    local begin=0
  else
    echo "unsupported shell" && return 1
  fi
  files=(`find "$1" -name "*.jar"`)

  for f in $files; do
    jarName=`basename $f`
    list=`unzip -l $f | awk -v fn=$jarName '/\.class$/{print $NF,fn}'`
    size=`echo "$list" | wc -l`
    echo $jarName $size >> $tmphash
    echo "$list"
  done | sort | awk 'NF{ a[$1]++;m[$1]=m[$1]","$2}END{for(i in a) if(a[i] > 1) print i,substr(m[i],2)}' > $tmpfile

  awk '{print $2}' $tmpfile | awk -F',' '{i=1;for(;i<=NF;i++) for(j=i+1;j<=NF;j++) print $i,$j}' | sort | uniq -c | sort -nrk1 |
  while read line; do
    local dup=${line%% *}
    local jars=${line#* }
    local jar1=${jars% *}
    local jar2=${jars#* }
    local len_jar1=`grep -F "$jar1" $tmphash | grep ^"$jar1" | awk '{print $2}'`
    local len_jar2=`grep -F "$jar2" $tmphash | grep ^"$jar2" | awk '{print $2}'`
    local len=$(($len_jar1 > $len_jar2 ? $len_jar1 : $len_jar2))
    local per=$(echo "scale=2; $dup/$len" | bc -l)
    echo ${per/./} $dup $jar1 $jar2
  done | sort -nr -k1 -k2 | awk 'NR==1{print "Similarity DuplicateClasses File1 File2"}{print "%"$0}'| column -t

  sort $tmpfile | awk '{print $1,"\n\t\t",$2}' > $verbose
  echo "See $verbose for more details."

  rm -f $tmpfile
  rm -f $tmphash
}
########################### wac ###########################

function cf_wac_pushf() {
  local server_url="http://uav.wacai.info/api/v2/file/upload"

  if [ $# -ne 1 ]; then
    echo "Just need file path, please." && return 1
  fi

  if [ ! -f "$1" ]; then
    echo "$1 Not a file." && return 1
  fi

  local url_s symlink
  url_s=$(curl -X POST -F file=@"$1" --progress-bar $server_url)
  symlink=$(echo "$url_s" | awk -F/ '{print $NF}')
  echo "该文件会保存7天，可以通过如下两种方式下载："
  echo "服务器的命令行: cf_wac_pullf $symlink"
  echo "浏览器下载: $url_s"
}

function cf_wac_pullf() {
  local server_url="http://uav.wacai.info"

  if [ $# -ne 1 ]; then
    echo "Need file code, please." && return 1
  fi

  local url="$server_url/api/v2/file/s/$1"
  local download_url filename
  download_url=$(curl -s "$url")
  filename=$(curl -sI "$download_url" | grep -o -E 'filename=.*$' | sed -e 's/filename=//' | tr -d '\r')
  curl -sS -o "$filename" "$download_url"
  echo "Save to $filename"
}
